JsonUtilityで基底クラスのリストをシリアライズする際に子クラスのデータを消さない方法

# 課題

以下のようなクラスがあったとして

```Person.cs
[System.Serializable]
public class Person
{
    public string name;
    public int age;
}

[System.Serializable]
public class Man : Person
{
    public string manField;
}

[System.Serializable]
public class Woman : Person
{
    public int womanField;
}
```

TestPersonListをJsonUtilityでシリアライズすると

```Test1.cs
    [System.Serializable]
    public class TestPersonList{
        public List<Person> personList = new List<Person>();
    }

    void TestPersonFailed () {
        var target = new List<Person> () {
            new Man { age = 18, name = "男性", manField = "man" },
            new Woman { age = 16, name = "女性", womanField = 100 }
        };
        var testPersonList = new TestPersonList(){personList = target};
        var json = JsonUtility.ToJson (testPersonList); // manField, womanFieldが消える
        Debug.Log (json);
    }
```

`manField`、`womanField`がJSONに含まれません。
<img width="494" alt="スクリーンショット 2019-04-08 0.33.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/113504/85afa700-69ca-97d2-35b4-8e1c44acbfdb.png">

これをJsonUtilityを使うことは維持しつつ解決したい。

# 対応
JsonUtilityでシリアライズできる形に整形する。

[リポジトリ](https://github.com/teach310/SerializeDerivedClass)

以下のようなスクリプトを作ることで解決できます。

```PersonList.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class PersonList {
	[SerializeField] List<int> manIndexList = new List<int> ();
	[SerializeField] List<Man> manList = new List<Man> ();
	[SerializeField] List<int> womanIndexList = new List<int> ();
	[SerializeField] List<Woman> womanList = new List<Woman> ();

	public PersonList (List<Person> src) {
		for (int i = 0; i < src.Count; i++) {
			switch (src[i]) {
				case Man man:
					manIndexList.Add (i);
					manList.Add (man);
					break;
				case Woman woman:
					womanIndexList.Add (i);
					womanList.Add (woman);
					break;

			}
		}
	}

	public List<Person> Convert () {
		int length =
			manIndexList.Count +
			womanIndexList.Count;
		var rtn = new Person[length];
		for (int i = 0; i < manIndexList.Count; i++) {
			rtn[manIndexList[i]] = manList[i];
		}
		for (int i = 0; i < womanIndexList.Count; i++) {
			rtn[womanIndexList[i]] = womanList[i];
		}
		return new List<Person> (rtn);
	}

	public static List<System.Type> GetAllDerivedTypes () {
		return new List<System.Type> () {
			typeof (Man),
			typeof (Woman)
		};
	}
}
```

JSON化、復元ともにできました。

<img width="889" alt="スクリーンショット 2019-04-08 0.33.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/113504/61e68f8f-a8f4-f52e-7fa9-abef6baacbed.png">

```Test2.cs
    void TestPersonListOK () {
        var target = new List<Person> () {
            new Man { age = 18, name = "男性", manField = "man" },
            new Woman { age = 16, name = "女性", womanField = 100 }
        };
        var personList = new PersonList (target);

        // Object -> string
        var json = JsonUtility.ToJson (personList);
        Debug.Log (json);

        // string -> Object
        var personList2 = JsonUtility.FromJson<PersonList> (json);
        var target2 = personList2.Convert ();
        var man = target2[0] as Man;
        Debug.Log ($"age={man.age},name={man.name}, manField={man.manField}");
        var woman = target2[1] as Woman;
        Debug.Log ($"age={woman.age},name={woman.name}, manField={woman.womanField}");
    }
```

# 問題点
PersonList作るの面倒すぎ

# 対応2
PersonListを自動生成する。

```DerivedClassListGenerator.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class DerivedClassListGenerator : CodeGenerator {
    public void CreateAt (Type baseType) {
        var derivedTypes = GetAllDerivedTypes (AppDomain.CurrentDomain, baseType);
        if (derivedTypes.Count == 0) {
            Debug.LogError ($"No Derived Class : {baseType.Name}");
            return;
        }
        // テンプレートをロード
        var templateLoader = new TemplateLoader ();
        var writer = new Writer ();
        var template = templateLoader.LoadTemplate (TemplatesDir + "DerivedClassList.txt");
        // 各項目の文言を生成
        string className = baseType.Name + "List";
        string fieldContent = GetField(derivedTypes);
        string switchContent = GetSwitch(derivedTypes);
        string convertContent = GetConvert(derivedTypes, baseType);
        string getAllDerivedTypesContent = GetGetAllDerivedTypes(derivedTypes);
        var content = template.text;
        content = content.Replace("$ClassName$", className);
        content = content.Replace("$Field$", fieldContent);
        content = content.Replace("$DataName$", baseType.Name);
        content = content.Replace("$Switch$", switchContent);
        content = content.Replace("$Convert$", convertContent);
        content = content.Replace("$GetAllDerivedTypes$", getAllDerivedTypesContent);
        // スクリプト生成
        writer.CreateNewScriptAt(className, content);
    }

    string GetField (List<Type> derivedTypes) {
        var stringBuilder = new StringBuilder ();
        bool isFirst = true;
        foreach (var type in derivedTypes) {
            string className = type.Name;
            string camelClassName = UpperCamelToCamel (className);
            string prefix = isFirst ? "" : Tabs(1);
            stringBuilder.AppendLine (prefix + $"[SerializeField] List<int> {camelClassName}IndexList = new List<int>();");
            stringBuilder.AppendLine (Tabs(1) + $"[SerializeField] List<{className}> {camelClassName}List = new List<{className}>();");
            isFirst = false;
        }
        return stringBuilder.ToString ();
    }

    string GetSwitch (List<Type> derivedTypes) {
        var stringBuilder = new StringBuilder ();
        bool isFirst = true;
        foreach (var type in derivedTypes) {
            string className = type.Name;
            string camelClassName = UpperCamelToCamel (className);
            string prefix = isFirst ? "" : Tabs(4);
            stringBuilder.AppendLine (prefix + $"case {className} {camelClassName}:");
            stringBuilder.AppendLine (Tabs(5) + $"{camelClassName}IndexList.Add(i);");
            stringBuilder.AppendLine (Tabs(5) + $"{camelClassName}List.Add({camelClassName});");
            stringBuilder.AppendLine (Tabs(5) + "break;");
            isFirst = false;
        }
        return stringBuilder.ToString ();
    }

    string GetConvert(List<Type> derivedTypes, Type baseType){
        var stringBuilder = new StringBuilder ();
        stringBuilder.AppendLine("int length = ");
        for (int i = 0; i < derivedTypes.Count; i++){
            string className = derivedTypes[i].Name;
            string camelClassName = UpperCamelToCamel (className);
            if(i == derivedTypes.Count -1 ){
                stringBuilder.AppendLine (Tabs(3) + $"{camelClassName}IndexList.Count;");
            }else{
                stringBuilder.AppendLine (Tabs(3) + $"{camelClassName}IndexList.Count +");
            }
        }
        stringBuilder.AppendLine(Tabs(2) + $"var rtn = new {baseType.Name}[length];");
        for (int i = 0; i < derivedTypes.Count; i++){
            string className = derivedTypes[i].Name;
            string camelClassName = UpperCamelToCamel (className);
            stringBuilder.AppendLine (Tabs(2) + $"for (int i = 0; i < {camelClassName}IndexList.Count; i++)");
            stringBuilder.AppendLine (Tabs(2) + "{");
            stringBuilder.AppendLine (Tabs(3) + $"rtn[{camelClassName}IndexList[i]] = {camelClassName}List[i];");
            stringBuilder.AppendLine (Tabs(2) + "}");
        }
        stringBuilder.Append(Tabs(2) +$"return new List<{baseType.Name}>(rtn);");
        return stringBuilder.ToString ();
    }

    string GetGetAllDerivedTypes(List<Type> derivedTypes){
        var stringBuilder = new StringBuilder ();
        for (int i = 0; i < derivedTypes.Count; i++){
            string className = derivedTypes[i].Name;
            string camelClassName = UpperCamelToCamel (className);
            if(i == derivedTypes.Count -1 ){
                stringBuilder.Append ( Tabs(3) + $"typeof({className})");
            }else{
                string prefix = i == 0 ? "" : Tabs(3);
                stringBuilder.AppendLine (prefix + $"typeof({className}),");
            }
        }
        return stringBuilder.ToString ();
    }

    public List<System.Type> GetAllDerivedTypes (System.AppDomain appDomain, System.Type baseType) {
        var result = new List<System.Type> ();
        var assemblies = appDomain.GetAssemblies ();
        for (int i = 0; i < assemblies.Length; i++) {
            var types = assemblies[i].GetTypes ();
            for (int j = 0; j < types.Length; j++) {
                if (types[j].IsSubclassOf (baseType)) {
                    result.Add (types[j]);
                }
            }
        }
        return result;
    }
}
```

```CodeGenerator.cs
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;

public class CodeGenerator {

    [System.Serializable]
    public class Field {
        public string name;
        public string fieldType;

        public string GetText (bool isPublic = true) {
            string format = isPublic ? "public {0} {1};" : "{0} {1};";
            return string.Format (format, fieldType, name);
        }
    }

    public static readonly string TemplatesDir = "Assets/Scripts/Editor/CodeGenerator/Templates/";

    public class TemplateLoader {
        public TextAsset LoadTemplate (string path) {
            return AssetDatabase.LoadAssetAtPath<TextAsset> (path);
        }
    }

    public class Writer {

        public void CreateNewScript (string path, string content) {
            using (StreamWriter streamWriter = File.CreateText (path + ".cs")) {
                streamWriter.WriteLine (content);
            }
            AssetDatabase.Refresh ();
        }

        public void CreateNewScriptAt (string fileName, string content) {
            var path = EditorUtility.OpenFolderPanel ("Select Create Folder", "", "");
            CreateNewScript (System.IO.Path.Combine (path, fileName), content);
        }
    }

    public string Tabs (int level) {
        string rtn = "";
        for (int i = 0; i < level; i++) {
            rtn += "\t";
        }
        return rtn;
    }

    public string UpperCamelToCamel (string src) {
        if (string.IsNullOrEmpty (src)) return src;
        return char.ToLowerInvariant (src[0]) + src.Substring (1, src.Length - 1);
    }
}
```

```DerivedClassList.txt
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class $ClassName$
{
    $Field$
    public $ClassName$(List<$DataName$> src){
        for (int i = 0; i < src.Count; i++)
        {
            switch (src[i])
            {
                $Switch$
            }
        }
    }

    public List<$DataName$> Convert(){
        $Convert$
    }

    public static List<System.Type> GetAllDerivedTypes(){
        return new List<System.Type>(){
            $GetAllDerivedTypes$
        };
    }
}
```

# まとめ
FullSerializerを使わずにJsonUtilityでがんばろうという試み
ゴリ押しですがいちおう達成はできました。

# 参考
https://answers.unity.com/questions/983125/c-using-reflection-to-automate-finding-classes.html