private static bool FilterElement(StringBuilder sb, object obj, object key, object val,
                                          bool pretty, bool includeType, bool showNulls, bool isArray, int depth, List <object> recursionStack,
                                          KeyValuePairToStringFilter filter = null)
        {
            bool unfiltered = true;

            if (filter != null)
            {
                string result = filter.Invoke(obj, key, val);
                unfiltered = result == null;
                if (!unfiltered && result.Length != 0)
                {
                    sb.Append(result); return(true);
                }
            }
            if (unfiltered)
            {
                if (!isArray)
                {
                    sb.Append(key).Append(pretty ? " : " : ":");
                }
                sb.Append(Stringify(val, pretty, includeType, showNulls, true, depth + 1, recursionStack));
                return(true);
            }
            return(false);
        }
        /// <summary>
        /// stringifies an object using custom NonStandard rules
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="pretty"></param>
        /// <param name="showType">include "=TypeName" if there could be ambiguity because of inheritance</param>
        /// <param name="depth"></param>
        /// <param name="rStack">used to prevent recursion stack overflows</param>
        /// <param name="filter">object0 is the object, object1 is the member, object2 is the value. if it returns null, print as usual. if returns "", skip print.</param>
        /// <param name="howToMarkStrongType">if null, use <see cref="StringifyTypeOfDictionary"/>, which is expected by <see cref="NonStandard.Data.CodeConvert.TryParse"/></param>
        /// <returns></returns>
        public static string Stringify(this object obj, bool pretty = true, bool showType = true, bool showNulls = false, bool showBoundary = true, int depth = 0, List <object> rStack = null, KeyValuePairToStringFilter filter = null, Func <Type, string> howToMarkStrongType = null)
        {
            if (obj == null)
            {
                return(showNulls ? "null" : "");
            }
            if (filter != null)
            {
                string res = filter.Invoke(obj, null, null); if (res != null)
                {
                    return(res);
                }
            }
            Type       t = obj.GetType();
            MethodInfo stringifyMethod = t.GetMethod("Stringify", Type.EmptyTypes);

            if (stringifyMethod != null)
            {
                return(stringifyMethod.Invoke(obj, Array.Empty <object>()) as string);
            }
            StringBuilder sb           = new StringBuilder();
            bool          showTypeHere = showType;    // no need to print type if there isn't type ambiguity

            if (showType)
            {
                Type b = t.BaseType;                 // if the parent class is a base class, there isn't any ambiguity
                if (b == typeof(ValueType) || b == typeof(System.Object) || b == typeof(Array) ||
                    t.GetCustomAttributes(false).FindIndex(o => o.GetType() == typeof(StringifyHideTypeAttribute)) >= 0)
                {
                    showTypeHere = false;
                }
            }
            if (obj is string || t.IsPrimitive || t.IsEnum)
            {
                return(StringifiedSimple(sb, obj, showBoundary).ToString());
            }
            if (rStack == null)
            {
                rStack = new List <object>();
            }
            int recursionIndex = rStack.IndexOf(obj);

            if (recursionIndex >= 0)
            {
                return(sb.Append("/* recursed " + (rStack.Count - recursionIndex) + " */").ToString());
            }
            rStack.Add(obj);
            Type listT = t.GetIListType();

            if (t.IsArray || listT != null)
            {
                StringifiedList(sb, obj, listT, depth, howToMarkStrongType, filter, rStack, pretty, showBoundary, showType, showTypeHere, showNulls);
            }
            else
            {
                StringifiedObject(sb, t, obj, depth, howToMarkStrongType, filter, rStack, pretty, showBoundary, showType, showTypeHere, showNulls);
            }
            if (sb.Length == 0)
            {
                sb.Append(obj.ToString());
            }
            return(sb.ToString());
        }