From 3f5b981f8eb8cfe9ba8779eb9a6513a75c5e7599 Mon Sep 17 00:00:00 2001
From: MaZiFAU <marco.alexander.zimmer@fau.de>
Date: Thu, 18 Aug 2022 19:17:24 +0200
Subject: [PATCH] +adjusted IJSONsavable & included auto pre-/ postprocessing
 unsing attributes;

---
 Assets/Scripts/IJSONsavable.cs                | 405 ++++++++++++++++++
 Assets/Scripts/IJSONsavable.cs.meta           |  11 +
 .../FactHandling/FactOrganizer.cs             |   2 +-
 .../FactHandling/SolutionOrganizer.cs         |   3 +
 Assets/Scripts/JSONManager.cs                 | 320 +-------------
 Assets/Scripts/Loading/Stage.cs               |  34 +-
 Assets/Scripts/StageStatic.cs                 |   2 +-
 .../UI/MainMenue/PageLoader/CreateLoader.cs   |   2 +-
 8 files changed, 440 insertions(+), 339 deletions(-)
 create mode 100644 Assets/Scripts/IJSONsavable.cs
 create mode 100644 Assets/Scripts/IJSONsavable.cs.meta

diff --git a/Assets/Scripts/IJSONsavable.cs b/Assets/Scripts/IJSONsavable.cs
new file mode 100644
index 00000000..83571ab2
--- /dev/null
+++ b/Assets/Scripts/IJSONsavable.cs
@@ -0,0 +1,405 @@
+using System.Collections.Generic;
+using System.Reflection;
+using System.Linq;
+using System.IO;
+using System;
+using Newtonsoft.Json;
+using UnityEngine;
+using static CommunicationEvents;
+
+
+// I would go for static virtual methods, but C#9 does not allow me...
+// static methods cannot be overwritten -> virtual
+public interface IJSONsavable<T> where T : IJSONsavable<T>, new()
+{
+    // stand-in for non static methods
+    public static readonly IJSONsavable<T> Instance = new T();
+
+    public static readonly FieldInfo[] JsonSaveableFields =
+    #region one-time-initialisation
+         typeof(T)
+        .GetFields(
+            BindingFlags.Instance |
+            BindingFlags.Public |
+            BindingFlags.NonPublic |
+            BindingFlags.Static)
+        .Where((field)
+            => !field.GetCustomAttributes().Any((attribute)
+                => attribute.GetType() == typeof(JsonIgnoreAttribute))
+            && field.FieldType.GetInterfaces().Any((inter)
+                => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>)))
+        .ToArray();
+    #endregion one-time-initialisation
+
+    public static readonly FieldInfo[] JsonAutoPreProcessFields =
+    #region one-time-initialisation
+         typeof(T)
+        .GetFields(
+            BindingFlags.Instance |
+            BindingFlags.Public |
+            BindingFlags.NonPublic |
+            BindingFlags.Static)
+        .Where((field)
+            => !field.GetCustomAttributes().Any((attribute)
+                => attribute.GetType() == typeof(JsonIgnoreAttribute))
+            && field.GetCustomAttributes().Any((attribute)
+                => attribute.GetType() == typeof(JSONsavable.JsonAutoPreProcessAttribute))
+            && field.FieldType.GetInterfaces().Any((inter)
+                => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>)))
+        .ToArray();
+    #endregion one-time-initialisation
+
+    public static readonly FieldInfo[] JsonAutoPostProcessFields =
+    #region one-time-initialisation
+         typeof(T)
+        .GetFields(
+            BindingFlags.Instance |
+            BindingFlags.Public |
+            BindingFlags.NonPublic |
+            BindingFlags.Static)
+        .Where((field)
+            => !field.GetCustomAttributes().Any((attribute)
+                => attribute.GetType() == typeof(JsonIgnoreAttribute))
+            && field.GetCustomAttributes().Any((attribute)
+                => attribute.GetType() == typeof(JSONsavable.JsonAutoPostProcessAttribute))
+            && field.FieldType.GetInterfaces().Any((inter)
+                => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>)))
+        .ToArray();
+    #endregion one-time-initialisation
+
+    public static readonly FieldInfo[] JsonSeperateFields =
+    #region one-time-initialisation
+         typeof(T)
+        .GetFields(
+            BindingFlags.Instance |
+            BindingFlags.Public |
+            BindingFlags.NonPublic |
+            BindingFlags.Static)
+        .Where((field)
+            => field.GetCustomAttributes().Any((attribute)
+                => attribute.GetType() == typeof(JSONsavable.JsonSeparateAttribute))
+            && field.FieldType.GetInterfaces().Any((inter)
+                => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>)))
+        .ToArray();
+    #endregion one-time-initialisation
+
+
+    // TODO: this?
+    public string name { get; set; }
+    public string path { get; set; }
+    protected static List<Directories>
+        hierarchie = new List<Directories> { Directories.misc };
+
+    #region OverridableMethods
+
+    public virtual string _IJGetName(string name) => name;
+    public virtual List<Directories> _IJGetHierarchie(List<Directories> hierarchie_base)
+    {
+        hierarchie_base ??= new List<Directories>();
+        return hierarchie_base.Concat(hierarchie).ToList();
+    }
+    public virtual bool _IJGetRawObject(out T payload, string path) => JSONsavable.ReadFromJsonFile<T>(out payload, path);
+    public virtual T _IJPreProcess(T payload) => payload;
+    public virtual T _IJPostProcess(T payload) => payload;
+
+    #endregion OverridableMethods
+
+
+    #region MethodTemplates
+
+    public bool store(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true)
+        => store(hierarchie, name, (T)this, use_install_folder, overwrite, deep_store);
+
+    public static bool store(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true)
+    {
+        var new_hierarchie =
+            Instance._IJGetHierarchie(hierarchie);
+        var new_name =
+            Instance._IJGetName(name);
+
+        string path = CreatePathToFile(out bool exists, new_name, "JSON", new_hierarchie, use_install_folder);
+        if (exists && !overwrite)
+            return false;
+
+        // store fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately
+        if (deep_store
+            && !store_children(hierarchie, name, payload, use_install_folder: false, overwrite, deep_store: true))
+            return false;
+
+        // store this
+        string path_o = payload.path;
+        payload.path = path;
+
+        var new_payload =
+            preprocess(payload);
+
+        payload.path = path_o;
+
+        JSONsavable.WriteToJsonFile(path, new_payload);
+        return true;
+    }
+
+    public bool store_children(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true)
+        => store_children(hierarchie, name, (T)this, use_install_folder, overwrite, deep_store);
+
+    public static bool store_children(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true)
+    {
+        var new_hierarchie =
+            Instance._IJGetHierarchie(hierarchie);
+        var new_name =
+            Instance._IJGetName(name);
+
+        for ((int max_i, bool success) = (0, true); max_i < JsonSeperateFields.Count(); max_i++)
+        {
+            var field = JsonSeperateFields[max_i];
+            dynamic save_me = field.GetValue(payload); // is S:IJSONsavable<S>
+
+            Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType);
+            Type[] store_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType, typeof(bool), typeof(bool), typeof(bool) };
+            object[] store_args = new object[] { new_hierarchie, new_name, save_me, use_install_folder, overwrite, deep_store };
+
+            var method = interface_type.GetMethod("store", store_args_type);
+            success &= (bool)method.Invoke(null, store_args);
+
+            // in case of no success: delete it again
+            if (!success)
+            {
+                delete_children(hierarchie, name, use_install_folder, JsonSeperateFields.Count() - max_i);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static bool load_children(List<Directories> hierarchie, string name, ref T raw_payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true)
+    {
+        var new_hierarchie =
+            Instance._IJGetHierarchie(hierarchie);
+        var new_name =
+            Instance._IJGetName(name);
+
+        bool success = true;
+        for (int max_i = 0; max_i < JsonSeperateFields.Count(); max_i++)
+        {
+            var field = JsonSeperateFields[max_i];
+
+            Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType);
+            Type[] load_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType.MakeByRefType(), typeof(bool), typeof(bool), typeof(bool) };
+            object[] load_args = new object[] { new_hierarchie, new_name, null, use_install_folder, deep_load, post_process };
+
+            var method = interface_type.GetMethod("load", BindingFlags.Public | BindingFlags.Static, null, load_args_type, null);
+            bool success_i = (bool)method.Invoke(null, load_args);
+
+            field.SetValue(raw_payload, success_i ? load_args[2] : Activator.CreateInstance(field.FieldType));
+            success &= success_i;
+        }
+
+        return success;
+    }
+
+    public static bool load(List<Directories> hierarchie, string name, out T payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true)
+    {
+        payload = default(T);
+        bool success = true;
+
+        var new_hierarchie =
+            Instance._IJGetHierarchie(hierarchie);
+        var new_name =
+            Instance._IJGetName(name);
+
+        string path = CreatePathToFile(out bool loadable, new_name, "JSON", new_hierarchie, use_install_folder);
+        if (!loadable)
+            return false;
+
+        if (!Instance._IJGetRawObject(out T raw_payload, path))
+            return false;
+        raw_payload.name = new_name;
+
+        // load fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately
+        if (deep_load
+            && !load_children(hierarchie, name, ref raw_payload, false /*use_install_folder*/))
+            success = false;
+
+        payload = post_process
+            ? postprocess(raw_payload)
+            : raw_payload;
+
+        return success;
+    }
+
+    public static T postprocess(T payload)
+    {
+        if (payload == null)
+            return Instance._IJPostProcess(payload);
+
+        for (int i = 0; i < JsonAutoPostProcessFields.Count(); i++)
+        {
+            var field = JsonAutoPostProcessFields[i];
+            dynamic process_me = field.GetValue(payload); // is S:IJSONsavable<S>
+
+            Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType);
+            Type[] process_args_type = new Type[] { field.FieldType };
+            object[] process_args = new object[] { process_me };
+
+            var method = interface_type.GetMethod("postprocess", process_args_type);
+            var processed = method.Invoke(null, process_args);
+
+            field.SetValue(payload, processed);
+        }
+
+        return Instance._IJPostProcess(payload);
+    }
+
+    public static T preprocess(T payload)
+    {
+        if (payload == null)
+            return Instance._IJPreProcess(payload);
+
+        for (int i = 0; i < JsonAutoPreProcessFields.Count(); i++)
+        {
+            var field = JsonAutoPreProcessFields[i];
+            dynamic process_me = field.GetValue(payload); // is S:IJSONsavable<S>
+
+            Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType);
+            Type[] process_args_type = new Type[] { field.FieldType };
+            object[] process_args = new object[] { process_me };
+
+            var method = interface_type.GetMethod("preprocess", process_args_type);
+            var processed = method.Invoke(null, process_args);
+
+            field.SetValue(payload, processed);
+        }
+
+        return Instance._IJPreProcess(payload);
+    }
+
+    public static void delete_children(List<Directories> hierarchie, string name, bool use_install_folder = false, int skip_last_children = 0)
+    {
+        var new_hierarchie =
+            Instance._IJGetHierarchie(hierarchie);
+        var new_name =
+            Instance._IJGetName(name);
+
+        for (int i = 0; i < JsonSeperateFields.Count() - skip_last_children; i++)
+        {
+            var field = JsonSeperateFields[i];
+
+            Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType);
+            Type[] delete_args_type = new Type[] { typeof(List<Directories>), typeof(string), typeof(bool) };
+            object[] delete_args = new object[] { new_hierarchie, new_name, use_install_folder };
+
+            var method = interface_type.GetMethod("delete", delete_args_type);
+            method.Invoke(null, delete_args);
+        }
+    }
+
+    public static bool delete(List<Directories> hierarchie, string name, bool use_install_folder = false)
+    {
+        var new_hierarchie =
+            Instance._IJGetHierarchie(hierarchie);
+        var new_name =
+            Instance._IJGetName(name);
+
+        string path = CreatePathToFile(out bool _, new_name, "JSON", new_hierarchie, use_install_folder);
+        if (!delete(path))
+            return false;
+
+        delete_children(hierarchie, name, use_install_folder);
+        return true;
+    }
+
+    // does not delete children!
+    private static bool delete(string path)
+    {
+        if (!File.Exists(path))
+            return false;
+
+        File.Delete(path);
+        return true;
+    }
+
+    // public bool delete() => delete(hierarchie, name);
+
+    #endregion MethodTemplates
+
+}
+
+public static class JSONsavable
+{
+    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
+    public sealed class JsonSeparateAttribute : Attribute { }
+
+    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
+    public sealed class JsonAutoPostProcessAttribute : Attribute { }
+
+    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
+    public sealed class JsonAutoPreProcessAttribute : Attribute { }
+
+    /// <summary>
+    /// Writes the given object instance to a Json file, recursively, including public members, excluding [JsonIgnore].
+    /// <para>Object type must have a parameterless constructor.</para>
+    /// <para>Only public properties and variables will be written to the file. These can be any type though, even other non-abstract classes.</para>
+    /// </summary>
+    /// <param name="filePath">The file path to write the object instance to.</param>
+    /// <param name="objectToWrite">The object instance to write to the file.</param>
+    public static bool WriteToJsonFile(string filePath, object objectToWrite)
+    {
+        // This tells your serializer that multiple references are okay.
+        var settings = new JsonSerializerSettings
+        {
+            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
+        };
+
+        TextWriter writer = null;
+        try
+        {
+            string payload = JsonConvert.SerializeObject(objectToWrite, settings);
+            writer = new StreamWriter(filePath);
+            writer.Write(payload);
+            return true;
+        }
+        catch (Exception e)
+        {
+            Debug.LogError(e);
+            return false;
+        }
+        finally
+        {
+            if (writer != null)
+                writer.Close();
+        }
+    }
+
+    /// <summary>
+    /// Reads an object instance from an Json file.
+    /// <para>Object type must have a parameterless constructor.</para>
+    /// </summary>
+    /// <typeparam name="T">The type of object to read from the file.</typeparam>
+    /// <param name="filePath">The file path to read the object instance from.</param>
+    /// <returns>Returns a new instance of the object read from the Json file.</returns>
+    public static bool ReadFromJsonFile<T>(out T payload, string filePath) where T : new()
+    {
+        payload = default(T);
+        TextReader reader = null;
+
+        try
+        {
+            reader = new StreamReader(filePath);
+            var fileContents = reader.ReadToEnd();
+            payload = JsonConvert.DeserializeObject<T>(fileContents);
+            return true;
+        }
+        catch (Exception e)
+        {
+            Debug.LogError(e);
+            return false;
+        }
+        finally
+        {
+            if (reader != null)
+                reader.Close();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/Assets/Scripts/IJSONsavable.cs.meta b/Assets/Scripts/IJSONsavable.cs.meta
new file mode 100644
index 00000000..35b3b75b
--- /dev/null
+++ b/Assets/Scripts/IJSONsavable.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 515d546c824caf241ac2fe28cb89ebf9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scripts/InteractionEngine/FactHandling/FactOrganizer.cs b/Assets/Scripts/InteractionEngine/FactHandling/FactOrganizer.cs
index 9e72d4b5..afe14cb7 100644
--- a/Assets/Scripts/InteractionEngine/FactHandling/FactOrganizer.cs
+++ b/Assets/Scripts/InteractionEngine/FactHandling/FactOrganizer.cs
@@ -679,7 +679,7 @@ public void fastforward()
     }
 
     FactOrganizer IJSONsavable<FactOrganizer>._IJPostProcess(FactOrganizer raw_payload)
-        => ReInitializeFactOrganizer<FactOrganizer>(raw_payload, false, out _);
+        => raw_payload == null ? raw_payload : ReInitializeFactOrganizer<FactOrganizer>(raw_payload, false, out _);
 
     /// <summary>
     /// Call this after assigning a stored instance in an empty world, that was not drawn.
diff --git a/Assets/Scripts/InteractionEngine/FactHandling/SolutionOrganizer.cs b/Assets/Scripts/InteractionEngine/FactHandling/SolutionOrganizer.cs
index c2882c8d..7faca0f6 100644
--- a/Assets/Scripts/InteractionEngine/FactHandling/SolutionOrganizer.cs
+++ b/Assets/Scripts/InteractionEngine/FactHandling/SolutionOrganizer.cs
@@ -144,6 +144,9 @@ public List<Fact> getMasterFactsByIndex (int i)
     string IJSONsavable<SolutionOrganizer>._IJGetName(string name) => name + endingVal;
     SolutionOrganizer IJSONsavable<SolutionOrganizer>._IJPostProcess(SolutionOrganizer raw_payload)
     {
+        if (raw_payload == null)
+            return raw_payload;
+
         SolutionOrganizer payload = 
             ReInitializeFactOrganizer<SolutionOrganizer>
             (raw_payload, false, out Dictionary<string, string> old_to_new);
diff --git a/Assets/Scripts/JSONManager.cs b/Assets/Scripts/JSONManager.cs
index afb7adfd..a0df7e51 100644
--- a/Assets/Scripts/JSONManager.cs
+++ b/Assets/Scripts/JSONManager.cs
@@ -1,244 +1,21 @@
 using System.Collections.Generic;
-using System.Reflection;
-using System.IO;
-using System;
-using System.Linq;
 using Newtonsoft.Json;
 using JsonSubTypes;
-using System.Collections;
-using UnityEngine;
-using Newtonsoft.Json.Linq;
-using static CommunicationEvents;
 
-// I would go for static virtual methods, but C#9 does not allow me...
-// static methods cannot be overwritten -> virtual
-public interface IJSONsavable<T> where T : IJSONsavable<T>, new()
-{
-    // stand-in for non static methods
-    public static readonly IJSONsavable<T> Instance = new T();
-    public static readonly FieldInfo[] JsonSeperateFields =
-         typeof(T)
-        .GetFields(
-            BindingFlags.Instance |
-            BindingFlags.Public |
-            BindingFlags.NonPublic |
-            BindingFlags.Static )
-        .Where((field)
-            => field.GetCustomAttributes().Any((attribute) 
-                => attribute.GetType() == typeof(JSONManager.JsonSeparateAttribute))
-            && field.FieldType.GetInterfaces().Any((inter) 
-                => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>)))
-        .ToArray();
-
-
-    // TODO: this?
-    public string name { get; set; }
-    public string path { get; set; }
-    protected static List<Directories>
-        hierarchie = new List<Directories> { Directories.misc };
-
-    #region OverridableMethods
-
-    public virtual string _IJGetName(string name) => name;
-    public virtual List<Directories> _IJGetHierarchie(List<Directories> hierarchie_base)
-    {
-        hierarchie_base ??= new List<Directories>();
-        return hierarchie_base.Concat(hierarchie).ToList();
-    }
-    public virtual bool _IJGetRawObject(out T payload, string path) => JSONManager.ReadFromJsonFile<T>(out payload, path);
-    public virtual T _IJPreProcess(T payload) => payload;
-    public virtual T _IJPostProcess(T payload) => payload;
-
-    #endregion OverridableMethods
-
-
-    #region MethodTemplates
-
-    public bool store(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true)
-        => store(hierarchie, name, (T) this, use_install_folder, overwrite, deep_store);
-
-    public static bool store(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true)
-    {
-        var new_hierarchie = 
-            Instance._IJGetHierarchie(hierarchie);
-        var new_name = 
-            Instance._IJGetName(name);
-
-        string path = CreatePathToFile(out bool exists, new_name, "JSON", new_hierarchie, use_install_folder);
-        if (exists && !overwrite)
-            return false;
-
-        // store fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately
-        if (deep_store
-            && !store_children(hierarchie, name, payload, use_install_folder: false, overwrite, deep_store: true))
-                return false;
-
-        // store this
-        string path_o = payload.path;
-        payload.path = path;
-
-        var new_payload =
-            Instance._IJPreProcess(payload);
-
-        payload.path = path_o;
-
-        JSONManager.WriteToJsonFile(path, new_payload);
-        return true;
-    }
-    
-    public bool store_children(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true)
-        => store_children(hierarchie, name, (T) this, use_install_folder, overwrite, deep_store);
-
-    public static bool store_children(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true)
-    {
-        var new_hierarchie =
-            Instance._IJGetHierarchie(hierarchie);
-        var new_name =
-            Instance._IJGetName(name);
-
-        for ((int max_i, bool success) = (0, true); max_i < JsonSeperateFields.Count(); max_i++)
-        {
-            var field = JsonSeperateFields[max_i];
-            dynamic save_me = field.GetValue(payload); // is S:IJSONsavable<S>
-
-            Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType);
-            Type[] store_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType, typeof(bool), typeof(bool), typeof(bool) };
-            object[] store_args = new object[] { new_hierarchie, new_name, save_me, use_install_folder, overwrite, deep_store };
-
-            var method = interface_type.GetMethod("store", store_args_type);
-            success &= (bool)method.Invoke(null, store_args);
-
-            // in case of no success: delete it again
-            if (!success)
-            {
-                delete_children(hierarchie, name, use_install_folder, JsonSeperateFields.Count() - max_i);
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    public static bool load_children(List<Directories> hierarchie, string name, ref T raw_payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true)
-    {
-        var new_hierarchie =
-            Instance._IJGetHierarchie(hierarchie);
-        var new_name =
-            Instance._IJGetName(name);
-
-        bool success = true;
-        for (int max_i = 0; max_i < JsonSeperateFields.Count(); max_i++)
-        {
-            var field = JsonSeperateFields[max_i];
-
-            Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType);
-            Type[] load_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType.MakeByRefType(), typeof(bool), typeof(bool), typeof(bool) };
-            object[] load_args = new object[] { new_hierarchie, new_name, null, use_install_folder, deep_load, post_process };
-
-            var method = interface_type.GetMethod("load", BindingFlags.Public | BindingFlags.Static, null, load_args_type, null);
-            bool success_i = (bool)method.Invoke(null, load_args);
-            
-            field.SetValue(raw_payload, success_i ? load_args[2] : Activator.CreateInstance(field.FieldType));
-            success &= success_i;
-        }
-
-        return success;
-    }
-
-    public static bool load(List<Directories> hierarchie, string name, out T payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true)
-    {
-        payload = default(T);
-        bool success = true;
-
-        var new_hierarchie =
-            Instance._IJGetHierarchie(hierarchie);
-        var new_name =
-            Instance._IJGetName(name);
-
-        string path = CreatePathToFile(out bool loadable, new_name, "JSON", new_hierarchie, use_install_folder);
-        if (!loadable)
-            return false;
-
-        if (!Instance._IJGetRawObject(out T raw_payload, path))
-            return false;
-        raw_payload.name = new_name;
-
-        // load fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately
-        if (deep_load
-            && !load_children(hierarchie, name, ref raw_payload, false /*use_install_folder*/))
-            success = false;
-
-        payload = post_process
-            ? Instance._IJPostProcess(raw_payload)
-            : raw_payload;
-
-        return success;
-    }
-
-    public static void delete_children(List<Directories> hierarchie, string name, bool use_install_folder = false, int skip_last_children = 0)
-    {
-        var new_hierarchie =
-            Instance._IJGetHierarchie(hierarchie);
-        var new_name =
-            Instance._IJGetName(name);
-
-        for(int i = 0; i < JsonSeperateFields.Count() - skip_last_children; i++) 
-        {
-            var field = JsonSeperateFields[i];
-
-            Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType);
-            Type[] delete_args_type = new Type[] { typeof(List<Directories>), typeof(string), typeof(bool) };
-            object[] delete_args = new object[] { new_hierarchie, new_name, use_install_folder };
-
-            var method = interface_type.GetMethod("delete", delete_args_type);
-            method.Invoke(null, delete_args);
-        }
-    }
-
-    public static bool delete(List<Directories> hierarchie, string name, bool use_install_folder = false)
-    {
-        var new_hierarchie =
-            Instance._IJGetHierarchie(hierarchie);
-        var new_name =
-            Instance._IJGetName(name);
-
-        string path = CreatePathToFile(out bool _, new_name, "JSON", new_hierarchie, use_install_folder);
-        if (!delete(path))
-            return false;
-
-        delete_children(hierarchie, name, use_install_folder);
-        return true;
-    }
-
-    // does not delete children!
-    private static bool delete(string path)
-    {
-        if (!File.Exists(path))
-            return false;
-
-        File.Delete(path);
-        return true;
-    }
-
-    // public bool delete() => delete(hierarchie, name);
-
-    #endregion MethodTemplates
-
-}
 
 public class MMTURICollection
 {
-    public string Point = "http://mathhub.info/MitM/core/geometry?3DGeometry?point";
-    public string Tuple = "http://gl.mathhub.info/MMT/LFX/Sigma?Symbols?Tuple";
-    public string LineType = "http://mathhub.info/MitM/core/geometry?Geometry/Common?line_type";
-    public string LineOf = "http://mathhub.info/MitM/core/geometry?Geometry/Common?lineOf";
-    public string OnLine = "http://mathhub.info/MitM/core/geometry?Geometry/Common?onLine";
-    public string Ded = "http://mathhub.info/MitM/Foundation?Logic?ded";
-    public string Eq = "http://mathhub.info/MitM/Foundation?Logic?eq";
-    public string Metric = "http://mathhub.info/MitM/core/geometry?Geometry/Common?metric";
-    public string Angle = "http://mathhub.info/MitM/core/geometry?Geometry/Common?angle_between";
-    public string Sketch = "http://mathhub.info/MitM/Foundation?InformalProofs?proofsketch";
-    public string RealLit = "http://mathhub.info/MitM/Foundation?RealLiterals?real_lit";
+    public string Tuple     = "http://gl.mathhub.info/MMT/LFX/Sigma?Symbols?Tuple";
+    public string Point     = "http://mathhub.info/MitM/core/geometry?3DGeometry?point";
+    public string LineType  = "http://mathhub.info/MitM/core/geometry?Geometry/Common?line_type";
+    public string LineOf    = "http://mathhub.info/MitM/core/geometry?Geometry/Common?lineOf";
+    public string OnLine    = "http://mathhub.info/MitM/core/geometry?Geometry/Common?onLine";
+    public string Metric    = "http://mathhub.info/MitM/core/geometry?Geometry/Common?metric";
+    public string Angle     = "http://mathhub.info/MitM/core/geometry?Geometry/Common?angle_between";
+    public string Ded       = "http://mathhub.info/MitM/Foundation?Logic?ded";
+    public string Eq        = "http://mathhub.info/MitM/Foundation?Logic?eq";
+    public string Sketch    = "http://mathhub.info/MitM/Foundation?InformalProofs?proofsketch";
+    public string RealLit   = "http://mathhub.info/MitM/Foundation?RealLiterals?real_lit";
 }
 
 public static class JSONManager 
@@ -371,79 +148,4 @@ public MMTValueDeclaration(string label, MMTTerm lhs, MMTTerm valueTp, MMTTerm v
             this.value = value;
         }
     }
-
-    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
-    public sealed class JsonSeparateAttribute : Attribute
-    {
-        public JsonSeparateAttribute() { }
-    }
-
-    // TODO? /// <para>If there are public properties/variables that you do not want written to the file, decorate them with the [JsonIgnore] attribute.</para>
-
-    /// <summary>
-    /// Writes the given object instance to a Json file, recursively to set depth, including all members.
-    /// <para>Object type must have a parameterless constructor.</para>
-    /// <para>Only All properties and variables will be written to the file. These can be any type though, even other non-abstract classes.</para>
-    /// </summary>
-    /// <param name="filePath">The file path to write the object instance to.</param>
-    /// <param name="objectToWrite">The object instance to write to the file.</param>
-    /// <param name="max_depth">The depth recursion will occur. Default = 0.</param>
-    public static bool WriteToJsonFile(string filePath, object objectToWrite, int max_depth = 0)
-    {
-        // This tells your serializer that multiple references are okay.
-        var settings = new JsonSerializerSettings
-        {
-            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
-        };
-
-        TextWriter writer = null;
-        try
-        {
-            string payload = JsonConvert.SerializeObject(objectToWrite, settings);
-            writer = new StreamWriter(filePath);
-            writer.Write(payload);
-            return true;
-        }
-        catch (Exception e)
-        {
-            Debug.LogError(e);
-            return false;
-        }
-        finally
-        {
-            if (writer != null)
-                writer.Close();
-        }
-    }
-
-    /// <summary>
-    /// Reads an object instance from an Json file.
-    /// <para>Object type must have a parameterless constructor.</para>
-    /// </summary>
-    /// <typeparam name="T">The type of object to read from the file.</typeparam>
-    /// <param name="filePath">The file path to read the object instance from.</param>
-    /// <returns>Returns a new instance of the object read from the Json file.</returns>
-    public static bool ReadFromJsonFile<T>(out T payload, string filePath) where T : new()
-    {
-        payload = default(T);
-        TextReader reader = null;
-
-        try
-        {
-            reader = new StreamReader(filePath);
-            var fileContents = reader.ReadToEnd();
-            payload = JsonConvert.DeserializeObject<T>(fileContents);
-            return true;
-        }
-        catch (Exception e)
-        {
-            Debug.LogError(e);
-            return false;
-        }
-        finally
-        {
-            if (reader != null)
-                reader.Close();
-        }
-    }
 }
diff --git a/Assets/Scripts/Loading/Stage.cs b/Assets/Scripts/Loading/Stage.cs
index 4da3ed34..e89eb914 100644
--- a/Assets/Scripts/Loading/Stage.cs
+++ b/Assets/Scripts/Loading/Stage.cs
@@ -32,7 +32,7 @@ public class Stage: IJSONsavable<Stage>
     /// <summary> Wether this <see cref="Stage"/> is located in installation folder or user data (a.k.a. !local).</summary>
     public bool use_install_folder = false;
 
-    [JsonIgnore, JSONManager.JsonSeparate]
+    [JsonIgnore, JSONsavable.JsonSeparate]
     public SaveGame savegame = null;
 
     /// <summary>
@@ -57,6 +57,7 @@ public Dictionary<string, PlayerRecord> player_record_list {
     /// Defining when this <see cref="Stage.player_record"/> is considered as solved.
     /// <seealso cref="FactOrganizer.DynamiclySolved(SolutionOrganizer, out List{List{string}}, out List{List{string}})"/>
     /// </summary>
+    [JSONsavable.JsonAutoPreProcess, JSONsavable.JsonAutoPostProcess]
     public SolutionOrganizer solution = null;
 
     /// <summary>
@@ -122,15 +123,6 @@ public Stage(string category, int number, string name, string description, strin
     }
 
 
-    Stage IJSONsavable<Stage>._IJPostProcess(Stage payload)
-    {
-        payload.solution = IJSONsavable<SolutionOrganizer>.Instance._IJPostProcess(payload.solution ?? new());
-        // // savegame is saved separately
-        // payload.savegame = IJSONsavable<SaveGame>.Instance._IJPostProcess(payload.savegame ?? new());
-
-        return payload;
-    }
-
     /// <summary>
     /// Sets members which are primitives.
     /// </summary>
@@ -344,7 +336,7 @@ public static bool ShallowLoad(out Stage set, string path)
         if (!IJSONsavable<Stage>.Instance._IJGetRawObject(out set, path))
             return false;
 
-        set = IJSONsavable<Stage>.Instance._IJPostProcess(set);
+        set = IJSONsavable<Stage>.postprocess(set);
         set.path = path;
         IJSONsavable<Stage>.load_children(null /*hierarchie*/, set.name, ref set, post_process: false);
 
@@ -446,8 +438,10 @@ public class SaveGame : IJSONsavable<SaveGame>
     public string name { get; set; }
     public string path { get; set; }
 
+    [JSONsavable.JsonAutoPreProcess, JSONsavable.JsonAutoPostProcess]
     public PlayerRecord player_record = new();
-    public Dictionary<string, PlayerRecord> player_record_list = new();
+
+    public Dictionary<string, PlayerRecord> player_record_list = new(); //entries are "PostProcess"ed when accessed/Cloned
 
     static SaveGame() 
     {
@@ -456,16 +450,6 @@ static SaveGame()
     public SaveGame() { }
 
     string IJSONsavable<SaveGame>._IJGetName(string name) => name + "_save";
-    SaveGame IJSONsavable<SaveGame>._IJPostProcess(SaveGame payload)
-    {
-        payload.player_record = IJSONsavable<PlayerRecord>.Instance._IJPostProcess(payload.player_record ?? new());
-
-        // // player_record_list entries are "PostProcess"ed when accessed/Cloned
-        //payload.player_record_list = new (payload.player_record_list.Select((v) => new KeyValuePair<string, PlayerRecord>
-        //    (v.Key, IJSONsavable<PlayerRecord>.Instance._IJPostProcess(v.Value))));
-
-        return payload;
-    }
 }
 
 
@@ -482,6 +466,7 @@ public class PlayerRecord: IJSONsavable<PlayerRecord>
     public double seconds = 0;
 
     /// <summary> Stage progress. </summary>
+    [JSONsavable.JsonAutoPreProcess, JSONsavable.JsonAutoPostProcess]
     public FactOrganizer factState = new();
     /// <summary> save game file name </summary>
     public string name { get; set; } = null;
@@ -507,11 +492,6 @@ public PlayerRecord(string name) {
         factState = new FactOrganizer();
     }
 
-    PlayerRecord IJSONsavable<PlayerRecord>._IJPostProcess(PlayerRecord payload)
-    {
-        payload.factState = IJSONsavable<FactOrganizer>.Instance._IJPostProcess(payload.factState);
-        return payload;
-    }
 
     /// <summary>
     /// Copies a specified <see cref="PlayerRecord"/>
diff --git a/Assets/Scripts/StageStatic.cs b/Assets/Scripts/StageStatic.cs
index 1af54911..17087d00 100644
--- a/Assets/Scripts/StageStatic.cs
+++ b/Assets/Scripts/StageStatic.cs
@@ -218,7 +218,7 @@ public static StageErrorStruct Validate(string category, int id, string name, st
         return new StageErrorStruct(
             category.Length == 0,
             ContainsNumber(category, id, true),
-            name.Length == 0 || ContainsKey(name, true) || ContainsKey(name, false),
+            name.Length == 0 || ContainsKey(name, local),
             false,
             !Worlds.Contains(scene),
             local == false,
diff --git a/Assets/Scripts/UI/MainMenue/PageLoader/CreateLoader.cs b/Assets/Scripts/UI/MainMenue/PageLoader/CreateLoader.cs
index 3341ff9f..2eb0a497 100644
--- a/Assets/Scripts/UI/MainMenue/PageLoader/CreateLoader.cs
+++ b/Assets/Scripts/UI/MainMenue/PageLoader/CreateLoader.cs
@@ -84,7 +84,7 @@ protected void Error(StageStatic.StageErrorStruct error)
         {
             throw new System.NotImplementedException("Id must be unique within a category"); // technichal a lie
         }
-        if (error.name) // for savegame identification (could be chained with local/category/id)
+        if (error.name) // for savegame identification (could be chained with local/category/id => beware StageStatic.GetStage(name, local))
         {
             throw new System.NotImplementedException("Name must be unique");
         }
-- 
GitLab