/// <summary>
        /// Deserializes a string into a boolean.
        /// </summary>
        /// <param name="str">The string to deserialize.</param>
        /// <param name="settings">The settings for how to handle certain parts.</param>
        /// <param name="location">OPTIONAL: Used to put a location on errors.</param>
        /// <returns>The deserialized boolean.</returns>
        public static bool DeserializeBool(string str, ABSaveSettings settings, int location = 0)
        {
            // Get a lower case version of the string for easier comparison.
            var lower = str.ToLower();

            // If it's "F", return false.
            if (lower == "f")
            {
                return(false);
            }

            // If it's "T", return true.
            else if (lower == "t")
            {
                return(true);
            }

            // Otherwise, there's a problem.
            else
            {
                settings.ErrorHandler.InvalidValueInABSaveWhenParsing(location, "The boolean given: " + str + " is not valid.");
            }

            // If we got here, it failed and so the value doesn't matter.
            return(false);
        }
        static bool AttemptToCreateIdentifiedType(Type typ, ABSaveSettings settings, bool useSB, StringBuilder sb, ref string identifiedType)
        {
            // Create the new string.
            var str = SerializeType(typ, useSB, sb);

            // If the number "settings.HighestIdentifiedType" is too high, we can't do it anymore in the future.
            if (settings.HighestIdentifiedType + 1 == short.MaxValue)
            {
                return(settings.CurrentlyCanIdentifyTypes = false);
            }

            // Now, add it as an item, so that this type can be identified easier in the future.
            settings.RememberedTypes.Add(new Helpers.ABSaveIdentifiedType(settings.HighestIdentifiedType++, str, typ));

            // We'll now write out the key for this type, as well as the actual type.
            if (useSB)
            {
                sb.Append(settings.RememberedTypes[settings.HighestIdentifiedType - 1].WrittenKey);
            }
            else
            {
                identifiedType = str + new string(settings.RememberedTypes[settings.HighestIdentifiedType - 1].WrittenKey);
            }

            return(true);
        }
        static bool AttemptToUseRememberedType(Type typ, ABSaveSettings settings, bool useSB, StringBuilder sb, out string identifiedType)
        {
            identifiedType = "";

            // If we can't remember types, then this won't work.
            if (!settings.RememberTypes)
            {
                return(false);
            }

            // Check for an already identifed type.
            var key = settings.SearchForIdentifiedType(typ);

            // If we can't find one, we'll attempt to create one, and if we can't even create one, then just write it.
            if (key == null && settings.CurrentlyCanIdentifyTypes)
            {
                return(AttemptToCreateIdentifiedType(typ, settings, useSB, sb, ref identifiedType));
            }
            else if (useSB)
            {
                sb.Append(key);
            }
            else
            {
                identifiedType = new string(key);
            }

            // Since we've got here without stopped, we were successful.
            return(true);
        }
Beispiel #4
0
 public void Setup()
 {
     Settings         = ABSaveSettings.ForSize;
     CurrentMap       = new ABSaveMap(Settings);
     CurrentGenerator = new MapGenerator();
     CurrentGenerator.Initialize(CurrentMap);
 }
Beispiel #5
0
        public static void WriteHeader(ABSaveType type, StringBuilder sb, ABSaveSettings settings, bool writeHeader)
        {
            // If we shouldn't write the header, then don't do it.
            if (!writeHeader)
            {
                return;
            }

            // Insert the type ("U" for unnamed and "N" for named and types, "V" for unnamed without types, "M" for named without types)
            if (settings.WithTypes)
            {
                sb.Append(type == ABSaveType.NoNames ? 'U' : 'N');
            }
            else
            {
                sb.Append(type == ABSaveType.NoNames ? 'V' : 'M');
            }

            // Insert version number - if needed.
            if (settings.HasVersion)
            {
                sb.Append(settings.Version);
            }

            // End the header off.
            sb.Append('\u0001');
        }
Beispiel #6
0
        public static ABSaveMap GetNonGeneric(Type type, ABSaveSettings settings)
        {
            var          map       = new ABSaveMap(settings);
            MapGenerator?generator = map.GetGenerator();

            map._rootItem = generator.GetMap(type);
            ReleaseGenerator(generator);
            return(map);
        }
        internal static void SerializeArrayItem(ABSaveType type, ABSaveSettings settings, bool useSB, StringBuilder sb, bool dnWriteEndLevel, ref string ret, ref bool notFirst, ref ABSavePrimitiveType lastType, dynamic item)
        {
            // Serialize the item and write to either the StringBuilder or the "ret"...
            ret += Serialize(item, type, settings, out lastType, useSB, sb, RequiresLowerInnerLevelSymbol(lastType) ? false : notFirst, dnWriteEndLevel);

            // Update the "notFirst" variable.
            if (!notFirst)
            {
                notFirst = true;
            }
        }
        /// <summary>
        /// Deserializes a string into a number (int, long, double etc.)
        /// </summary>
        /// <param name="str">The string to deserialize.</param>
        /// <param name="settings">The settings for how to handle certain parts.</param>
        /// <param name="location">OPTIONAL: Used to put a location on errors.</param>
        /// <param name="targetType">Used to specify what target number type you want (int, long, double etc.)</param>
        /// <returns>The deserialized value.</returns>
        public static object DeserializeNumber(string str, Type targetType, ABSaveSettings settings, int location = 0)
        {
            // Attempt to parse it.
            var passed = decimal.TryParse(str, out decimal result);

            // If it fails to parse, the value isn't valid, so that will throw an error.
            if (!passed)
            {
                settings.ErrorHandler.InvalidValueInABSaveWhenParsing(location, "The number given: " + str + " is not valid for the type: " + targetType.Name);
                return(null);
            }

            // Otherwise, go ahead and return it!
            return(Convert.ChangeType(result, targetType));
        }
Beispiel #9
0
        public void Initialize(ABSaveSettings template, Dictionary <Type, uint> targetVersions = null, bool lazyWriteCompressed = false)
        {
            var settings = template.Customize(b => b
                                              .SetLazyWriteCompressed(lazyWriteCompressed)
                                              .AddConverter <BaseTypeConverter>()
                                              .AddConverter <SubTypeConverter>()
                                              .AddConverter <OtherTypeConverter>()
                                              );

            CurrentMap = ABSaveMap.Get <EmptyClass>(settings);

            Stream       = new MemoryStream();
            Serializer   = CurrentMap.GetSerializer(Stream, targetVersions);
            Deserializer = CurrentMap.GetDeserializer(Stream);
        }
        /// <summary>
        /// Serializes a dictionary.
        /// </summary>
        /// <param name="obj">The dictionary to serialize.</param>
        /// <param name="useSB">Whether this will write to a string builder (if true), or return a string (if false).</param>
        /// <param name="sb">The StringBuilder to write to - if <paramref name="useSB"/> is true.</param>
        /// <param name="dnWriteEndLevel">"Do Not Write End Level Symbol" - Marks whether to NOT write \u0005 (if true), commonly used for the last object of all.</param>
        /// <returns>If <paramref name="useSB"/> is false, this method will return the result as a string.</returns>
        public static string SerializeDictionary(dynamic obj, ABSaveType type, ABSaveSettings settings, bool useSB = false, StringBuilder sb = null, bool dnWriteEndLevel = false)
        {
            // Create a variable to store what we'll return.
            var ret = "";

            // Keep track of whether we're on the first item or not.
            var notFirst = false;

            // Write the opening for the array.
            ret += ABSaveWriter.WriteDictionaryOpening(useSB, sb);

            // Now, go through each item in the dictionary.
            foreach (var element in obj)
            {
                // If this isn't the first item, write the "next item" character.
                if (notFirst)
                {
                    ret += ABSaveWriter.WriteNextItem(true, useSB, sb);
                }

                // Add the key to it.
                if (useSB)
                {
                    sb.Append(element.Key);
                }
                else
                {
                    ret += element.Key;
                }

                // Serialize the item and write to either the StringBuilder or the "ret"
                ret += Serialize(element.Value, type, settings, useSB, sb, true, dnWriteEndLevel);

                // Update the "notFirst" variable if needed.
                if (!notFirst)
                {
                    notFirst = true;
                }
            }

            // Write the closing for the dictionary.
            ret += ABSaveWriter.WriteObjectClose(dnWriteEndLevel, useSB, sb);

            // Now, "ret" would be empty if we were using a StringBuilder, however, if we weren't... It will have the correct string in it so return it.
            return(ret);
        }
        /// <summary>
        /// Writes the type data that goes before an object or array.
        /// Which usually includes a key.
        /// </summary>
        /// <param name="typ">The type to write as a string.</param>
        /// <param name="useSB">Whether we are writing to a StringBuilder or not.</param>
        /// <param name="sb">The StringBuilder to write to - if we're writing to one at all.</param>
        /// <param name="settings">The settings for how to handle certain parts.</param>
        /// <returns>Returns the type as a string.</returns>
        public static string SerializeTypeBeforeObject(Type typ, ABSaveSettings settings, bool useSB = false, StringBuilder sb = null)
        {
            // Don't do anything if we're not meant to.
            if (!settings.WithTypes)
            {
                return("");
            }

            // Attempt use a remembered type (or create one) - and return with that.
            var attempt = AttemptToUseRememberedType(typ, settings, useSB, sb, out string res);

            // If that attempt was successful, then we'll use that.
            if (attempt)
            {
                return(res);
            }

            // If we weren't able to use an identified type, then we'll just have to write the type, not bothering with any of the identified type..
            return(SerializeType(typ, useSB, sb));
        }
Beispiel #12
0
        /// <summary>
        /// Creates a new ABSaveParser with all the correct configuration.
        /// </summary>
        /// <param name="objType">The type of the object to parse.</param>
        /// <param name="type">The way to handle the ABSave string.</param>
        /// <param name="settings">The settings for it.</param>
        public ABSaveParser(ABSaveType type, ABSaveSettings settings)
        {
            // Set all the tokens for the ABSave.
            Tokens = new System.Collections.ObjectModel.ObservableCollection <ABParserToken>()
            {
                new ABParserToken(nameof(ABSaveTokens.NextItem), '\u0001'),
                new ABParserToken(nameof(ABSaveTokens.Null), '\u0002'),
                new ABParserToken(nameof(ABSaveTokens.StartObject), '\u0003'),
                new ABParserToken(nameof(ABSaveTokens.StartArray), '\u0004'),
                new ABParserToken(nameof(ABSaveTokens.ExitObject), '\u0005'),
                new ABParserToken(nameof(ABSaveTokens.StartDictionary), '\u0006'),
            };

            // Set the correct settings.
            Settings = settings;
            Settings.ErrorHandler.ErrorEncountered += (e) => IsProcessing = false;

            // Set the type for now.
            ABSaveType = type;
        }
 /// <summary>
 /// Figures out what a string is and how to deserialize it. This does not deserialize objects, arrays or dictionaries, however.
 /// </summary>
 /// <param name="str">The string to deserialize.</param>
 /// <param name="objType">The type of the object to deserialize to.</param>
 /// <param name="settings">The settings for how to handle certain parts.</param>
 /// <param name="location">OPTIONAL: This is used to add a location to the error from the <paramref name="settings"/></param>
 /// <returns>The deserialized object - can be null if it needs to be manually parsed.</returns>
 public static object Deserialize(string str, Type objType, ABSaveSettings settings, int location = 0)
 {
     return(Deserialize(str, objType, settings, out ABSavePrimitiveType none, out bool none2, location));
 }
Beispiel #14
0
 public static ABSaveMap Get <T>(ABSaveSettings settings) => GetNonGeneric(typeof(T), settings);
Beispiel #15
0
 // Internal for use with unit tests.
 internal ABSaveMap(ABSaveSettings settings)
 {
     Settings  = settings;
     _allTypes = new Dictionary <Type, Converter?>();
 }
 /// <summary>
 /// Deserializes a string into a number (int, long, double etc.)
 /// </summary>
 /// <param name="str">The string to deserialize.</param>
 /// <param name="settings">The settings for how to handle certain parts.</param>
 /// <param name="location">OPTIONAL: Used to put a location on errors.</param>
 /// <typeparam name="T">Used to specify what target number type you want (int, long, double etc.)</param>
 /// <returns>The deserialized value.</returns>
 public static T DeserializeNumber <T>(string str, ABSaveSettings settings, int location = 0)
 {
     return((T)DeserializeNumber(str, typeof(T), settings, location));
 }
Beispiel #17
0
 public void SetupSettings()
 {
     Settings = ABSaveSettings.ForSpeed;
 }
Beispiel #18
0
 internal CheckTypeInfo(Type type, ABSaveSettings settings)
 {
     Type     = type;
     Settings = settings;
 }
 /// <summary>
 /// Deserializes a string into a DateTime - the string must be in ticks.
 /// </summary>
 /// <param name="str">The string to deserialize.</param>
 /// <param name="settings">The settings for how to handle certain parts.</param>
 /// <param name="location">OPTIONAL: Used to put a location on errors.</param>
 /// <returns>The deserialized DateTime.</returns>
 public static DateTime DeserializeDateTime(string str, ABSaveSettings settings, int location = 0)
 {
     // Just return a new DateTime, but with the number deserialized.
     return(new DateTime(DeserializeNumber <long>(str, settings, location)));
 }
 /// <summary>
 /// Serializes an object. The object could be anything.
 /// </summary>
 /// <param name="obj">The object to serialize</param>
 /// <param name="useSB">Whether this will write to a string builder (if true), or return a string (if false).</param>
 /// <param name="sb">The StringBuilder to write to - if <paramref name="useSB"/> is true.</param>
 /// <param name="writeNextInstructionSymbol">Whether it will write \u0001 on the start - usually false if it is serializing the first object in a class.</param>
 /// <param name="dnWriteEndLevel">"Do Not Write End Level Symbol" - Marks whether to NOT write \u0005 (if true), commonly used for the last object of all.</param>
 /// <param name="settings">The settings for how to handle certain parts.</param>
 /// <returns>If <paramref name="useSB"/> is false, this method will return the result as a string.</returns>
 public static string Serialize(dynamic obj, ABSaveType type, ABSaveSettings settings, bool useSB = false, StringBuilder sb = null, bool writeNextInstructionSymbol = true, bool dnWriteEndLevel = false)
 {
     return(Serialize(obj, type, settings, out ABSavePrimitiveType _, useSB, sb, writeNextInstructionSymbol, dnWriteEndLevel));
 }
        /// <summary>
        /// Figures out what a string is and how to deserialize it. This does not deserialize objects, arrays or dictionaries, however.
        /// </summary>
        /// <param name="str">The string to deserialize.</param>
        /// <param name="objType">The type of the object to deserialize to.</param>
        /// <param name="determinedType">The primitive that the object has been determined as.</param>
        /// <param name="manuallyParse">Whether a parser using this should manually parse the object or not.</param>
        /// <param name="settings">The settings for how to handle certain parts.</param>
        /// <param name="location">OPTIONAL: This is used to add a location to the error from the <paramref name="settings"/></param>
        /// <returns>The deserialized object - can be null if it needs to be manually parsed.</returns>
        public static object Deserialize(string str, Type objType, ABSaveSettings settings, out ABSavePrimitiveType determinedType, out bool manuallyParse, int location = 0)
        {
            // Set the "determinedType" to Unknown for now, and, manuallyParse to false.
            determinedType = ABSavePrimitiveType.Unknown;
            manuallyParse  = false;

            // Get a type code.
            var tCode = Type.GetTypeCode(objType);

            // Now, go through what it could be and convert it.
            if (objType == typeof(string))
            {
                determinedType = ABSavePrimitiveType.String;
                return(str);
            }

            // Check if it's a number.
            else if (ABSaveUtils.IsNumericType(tCode))
            {
                determinedType = ABSavePrimitiveType.Number;
                return(DeserializeNumber(str, objType, settings, location));
            }

            // Check if it's an array - if so, all we can do is set the "determinedType" to "array" since this method doesn't deal with that.
            else if (ABSaveUtils.IsArray(objType))
            {
                manuallyParse  = true;
                determinedType = ABSavePrimitiveType.Array;
            }

            // Check if it's a dictionary - if so, all we can do is set the "determinedType" to "dictionary" since this method doesn't deal with that.
            else if (ABSaveUtils.IsDictionary(objType))
            {
                manuallyParse  = true;
                determinedType = ABSavePrimitiveType.Dictionary;
            }

            // Check if it's a boolean - if so, we can get the result out of that using "DeserializeBool"
            else if (objType == typeof(bool))
            {
                determinedType = ABSavePrimitiveType.Boolean;
                return(DeserializeBool(str, settings, location));
            }

            // Check if it's a DateTime - if so, we can get the result out of that using "DeserializeDateTime"
            else if (objType == typeof(DateTime))
            {
                determinedType = ABSavePrimitiveType.DateTime;
                return(DeserializeDateTime(str, settings, location));
            }

            // Check if it's a Type - if so, we can get the result using "Type.GetType()"
            else if (objType == typeof(Type))
            {
                determinedType = ABSavePrimitiveType.Type;
                return(Type.GetType(str));
            }

            // If it wasn't any of the above - it's probably an object, now, we've been given a string which is actually the type name of it (unless the ABSave is incorrect).
            else
            {
                // Mark it as an object.
                determinedType = ABSavePrimitiveType.Object;

                // We're going to try and find a TypeConverter. But if we can't find one - we'll just return the correct type based on the TypeName we were given.
                TypeConverter typeConv = TypeDescriptor.GetConverter(objType);

                // Check if the type converter can actually convert it FROM a string, if it can, use it.
                if (typeConv.CanConvertFrom(typeof(string)))
                {
                    return(typeConv.ConvertFrom(str));
                }

                // However, otherwise, since we know what the type is - we'll return that, and let the parser manually parse it.
                else
                {
                    manuallyParse = true;
                    return(Type.GetType(str));
                }
            }

            // If we got here it was probably manually making the parser do the rest to return null.
            return(null);
        }
        /// <summary>
        /// Serializes an object. The object could be anything.
        /// </summary>
        /// <param name="obj">The object to serialize</param>
        /// <param name="useSB">Whether this will write to a string builder (if true), or return a string (if false).</param>
        /// <param name="sb">The StringBuilder to write to - if <paramref name="useSB"/> is true.</param>
        /// <param name="determinedType">The primitive type which has been decided for it.</param>
        /// <param name="writeNextInstructionSymbol">Whether it will write \u0001 on the start - usually false if it is serializing the first object in a class.</param>
        /// <param name="dnWriteEndLevel">"Do Not Write End Level Symbol" - Marks whether to NOT write \u0005 (if true), commonly used for the last object of all.</param>
        /// <returns>If <paramref name="useSB"/> is false, this method will return the result as a string.</returns>
        public static string Serialize(dynamic obj, ABSaveType type, ABSaveSettings settings, out ABSavePrimitiveType determinedType, bool useSB = false, StringBuilder sb = null, bool writeNextInstructionSymbol = true, bool dnWriteEndLevel = false)
        {
            // This will be what to return if useSB is false.
            string ret;

            // For now, make it so we output "unknown".
            determinedType = ABSavePrimitiveType.Unknown;

            // Check if the object is null... or an IntPtr, write the null symbol - otherwise, we could get a StackOverflowException.
            if (obj == null || obj is IntPtr || obj is UIntPtr)
            {
                return(ABSaveWriter.WriteNullItem(useSB, sb));
            }

            // Remember what type the object is - as well as the TypeCode.
            Type objType = obj.GetType();
            var  tCode   = Type.GetTypeCode(obj.GetType());

            // If the object is a string - write it as a string.
            if (tCode == TypeCode.String)
            {
                ret            = ABSaveWriter.WriteString(obj, writeNextInstructionSymbol, useSB, sb);
                determinedType = ABSavePrimitiveType.String;
            }

            // If the object is a number - write it as a number.
            else if (IsNumericType(tCode))
            {
                ret            = ABSaveWriter.WriteNumerical(obj, tCode, true, useSB, sb);
                determinedType = ABSavePrimitiveType.Number;
            }

            // If the object is an array - serialize it as an array.
            else if (IsArray(objType))
            {
                ret            = SerializeArray(obj, objType, type, settings, useSB, sb, dnWriteEndLevel);
                determinedType = ABSavePrimitiveType.Array;
            }

            // If the object is a dictionary - serialize it as a dictionary.
            else if (IsDictionary(objType))
            {
                ret            = SerializeDictionary(obj, type, settings, useSB, sb, dnWriteEndLevel);
                determinedType = ABSavePrimitiveType.Dictionary;
            }

            // If the object is a boolean - serialize it as a boolean.
            else if (tCode == TypeCode.Boolean)
            {
                ret            = SerializeBool(obj, writeNextInstructionSymbol, useSB, sb);
                determinedType = ABSavePrimitiveType.Boolean;
            }

            // If the object is a DateTime - serialize it as a DateTime.
            else if (tCode == TypeCode.DateTime)
            {
                ret            = SerializeDateTime(obj, useSB, sb);
                determinedType = ABSavePrimitiveType.DateTime;
            }

            // If it's a type, just write it out using the ABSaveWriter (for some reason there is no TypeConverter built-in for a type!)
            else if (obj is Type)
            {
                ret            = SerializeType(obj, useSB, sb);
                determinedType = ABSavePrimitiveType.Type;
            }

            // Otherwise, we'll attempt to find a built-in type converter (to a string)
            else
            {
                // Mark it as an object.
                determinedType = ABSavePrimitiveType.Object;

                // Attempt to get a converter for it.
                var canBeTypeConverted = false;
                var typeConv           = TypeDescriptor.GetConverter(objType);

                // Check if the type converter can actually convert it to a string.
                if (typeConv.IsValid(obj))
                {
                    if (typeConv.CanConvertTo(typeof(string)))
                    {
                        canBeTypeConverted = true;
                    }
                }

                // If it can be type converted, convert it using that, and then write it as a string.
                if (canBeTypeConverted)
                {
                    ret = ABSaveWriter.WriteString(typeConv.ConvertToString(obj), writeNextInstructionSymbol, useSB, sb);
                }

                // Otherwise, if it can't be type converted... Manually convert it.
                else
                {
                    ret = SerializeObject(obj, type, objType, settings, writeNextInstructionSymbol, useSB, sb, dnWriteEndLevel);
                }
            }

            // Return the result from this.
            return(ret);
        }
        /// <summary>
        /// Serializes an object, by using <see cref="ABSaveConvert"/>, but some extras.
        /// </summary>
        /// <param name="obj">The object to serialize manually.</param>
        /// <param name="objType">The type of the object to serialize manually.</param>
        /// <param name="useSB">Whether this will write to a string builder (if true), or return a string (if false).</param>
        /// <param name="sb">The StringBuilder to write to - if <paramref name="useSB"/> is true.</param>
        /// <param name="dnWriteEndLevel">"Do Not Write End Level Symbol" - Marks whether to NOT write \u0005 (if true), commonly used for the last object of all.</param>
        /// <param name="settings">The settings for how to handle certain parts.</param>
        /// <returns>If <paramref name="useSB"/> is false, this method will return the result as a string.</returns>
        public static string SerializeObject(object obj, ABSaveType type, Type objType, ABSaveSettings settings, bool writeNextInstructionSymbol = true, bool useSB = false, StringBuilder sb = null, bool dnWriteEndLevel = false)
        {
            // Create a variable to store what we'll return - if we aren't using a StringBuilder.
            var ret = "";

            // First, write a "next step" symbol.
            ret += ABSaveWriter.WriteNextItem(writeNextInstructionSymbol, useSB, sb);

            // Next, serialize the type that goes before it.
            ret += SerializeTypeBeforeObject(objType, settings, useSB, sb);

            // Then, write the opening (\u0003) for the object.
            ret += ABSaveWriter.WriteObjectOpen(useSB, sb);

            // And, write the actual object, use the correct method for either string or for a StringBuilder.
            ret += ABSaveConvert.ObjectToABSave(obj, type, settings, useSB, sb);

            // Finally, write the ending for the object.
            ret += ABSaveWriter.WriteObjectClose(dnWriteEndLevel, useSB, sb);

            // Now, "ret" would be empty if we were using a StringBuilder, however, if we weren't... It will have the correct string in it so return it.
            return(ret);
        }
Beispiel #24
0
 public void Setup <T>(ABSaveSettings settings, Dictionary <Type, uint> targetVersions = null)
 {
     Initialize(settings, targetVersions);
     ResetStateWithMapFor <T>();
 }
        /// <summary>
        /// An object (with multiple properties) to serialize manually - one that doesn't have a TypeConverter.
        /// </summary>
        /// <param name="obj">The object to serialize manually</param>
        /// <param name="objType">The type of the object.</param>
        /// <param name="useSB">Whether this will write to a string builder (if true), or return a string (if false).</param>
        /// <param name="sb">The StringBuilder to write to - if <paramref name="useSB"/> is true.</param>
        /// <param name="dnWriteEndLevel">"Do Not Write End Level Symbol" - Marks whether to NOT write \u0005 (if true), commonly used for the last object of all.</param>
        /// <returns>If <paramref name="useSB"/> is false, this method will return the result as a string.</returns>
        public static string SerializeArray(dynamic obj, Type objType, ABSaveType type, ABSaveSettings settings, bool useSB = false, StringBuilder sb = null, bool dnWriteEndLevel = false)
        {
            obj = obj as IEnumerable;

            // Create a variable to store what we'll return.
            var ret = "";

            // Keep track of whether we're on the first item or not.
            var notFirst = false;

            // Write the opening for the array.
            ret += ABSaveWriter.WriteArrayOpening(useSB, sb);

            // Keep track of what type the last property was - this allows us to decide whether to add the Next Item character to the next item.
            var lastType = ABSavePrimitiveType.Unknown;

            // If it's an array, just use a "for" loop.
            if (objType.IsArray)
            {
                for (var i = 0; i < obj.Length; i++)
                {
                    SerializeArrayItem(type, settings, useSB, sb, dnWriteEndLevel, ref ret, ref notFirst, ref lastType, obj[i]);
                }
            }

            // For anything else, foreach will work fine.
            else
            {
                foreach (var item in obj)
                {
                    SerializeArrayItem(type, settings, useSB, sb, dnWriteEndLevel, ref ret, ref notFirst, ref lastType, item);
                }
            }

            // Write the closing for the array.
            ret += ABSaveWriter.WriteObjectClose(dnWriteEndLevel, useSB, sb);

            // Now, "ret" would be empty if we were using a StringBuilder, however, if we weren't... It will have the correct string in it so return it.
            return(ret);
        }